/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C) 2011 - 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C) 2011 - 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.vst.binaural;

import java.util.ArrayList;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 *
 * @author N_H <h.10x64@gmail.com>
 */
public class SoundEnvironment {

    private static final double FLICTION = 0.3;
    private ArrayList<UpdateListener> updateListener;
    private ArrayList<ChangeListener> changeListener;
    private Observer observer;
    private Source source;
    private double width, height, depth;
    private boolean isChanged;

    public SoundEnvironment(double width, double height, double depth) {
        observer = new Observer();
        source = new Source();
        source.setPosition(new Vector3(0, 0, 1.0));
        this.width = width;
        this.height = height;
        this.depth = depth;
        isChanged = false;
        updateListener = new ArrayList<UpdateListener>();
        changeListener = new ArrayList<ChangeListener>();
    }

    public Vector3 getSourcePosition() {
        return source.getPosition();
    }

    public Spherical getSourceDirection() {
        return null;
    }

    public Vector3 getObserverPosition() {
        return observer.getPosition();
    }

    public Spherical getObserverDirection() {
        return observer.getDirection();
    }

    /**
     * 音が聞こえてくる方向と距離を取得します。<br/>
     * getSourcePositionとは違い、このメソッドは観測点の向きを考慮に入れた上で観測点が観測する(相対的な)音の位置を返します。
     * 
     * @return 
     */
    public Spherical getSoundPosition() {
        Vector3 vRelPos = source.getPosition();

        Matrix4 revMat = Matrix4.getReverse(observer.posture);
        revMat.transform(vRelPos);

        Spherical sRelPos = vRelPos.toSpherical();

        return sRelPos;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

    public double getDepth() {
        return depth;
    }

    public void setObserverDirection(Spherical s) {
        setObserverPosture(observer.posture.getPosition(), s);
    }

    public void setObserverDirection(double elev, double azim) {
        setObserverDirection(new Spherical(elev, azim, 1.0));
    }

    public void setObserverPosition(Vector3 v) {
        setObserverPosture(v, observer.posture.getDirection());
    }

    public void setObserverPosture(Vector3 pos, Spherical dir) {
        Matrix4 posture = Matrix4.getRot(dir.elev, dir.azim, 0, Matrix4.ZXY);
        setObserverPosture(posture);
    }

    public void setObserverPosture(Matrix4 mat) {
        observer.posture = new Matrix4(mat);
        isChanged = true;
        update(0);
    }

    public void setSourcePosition(Spherical s) {
        setSourcePosition(s.toVector3());
    }

    public void setSourcePosition(Vector3 v) {
        if (!v.equals(source.getPosition())) {
            Vector3 relPos = Vector3.add(v, Vector3.mul(-1, observer.getPosition()));
            if ((relPos.y >= 0)
                    || ((relPos.y < 0) && (Math.pow(relPos.x, 2) + Math.pow(relPos.z, 2) > Math.pow(relPos.y, 2)))) {
                source.setPosition(v);
                isChanged = true;
                update(0);
            }
        }
    }

    public void setSourcePosition(double x, double y, double z) {
        setSourcePosition(new Vector3(x, y, z));
    }

    public void update(double dt) {
        fireUpdateEvent(dt);

        Vector3 lastSrcPos = new Vector3(source.getPosition());
        Spherical lastObsDir = new Spherical(observer.getDirection());

        if (isChanged || !lastSrcPos.equals(source.getPosition()) || !lastObsDir.equals(observer.getDirection())) {
            fireChangeEvent();
            isChanged = false;
        }
    }

    public boolean addChangeListener(ChangeListener listener) {
        return changeListener.add(listener);
    }

    public boolean removeChangeListener(ChangeListener listener) {
        return changeListener.remove(listener);
    }

    public boolean addUpdateListener(UpdateListener listener) {
        return updateListener.add(listener);
    }

    public boolean removeUpdateListener(UpdateListener listener) {
        return updateListener.remove(listener);
    }

    private void fireChangeEvent() {
        ChangeEvent e = new ChangeEvent(this);
        for (int i = 0; i < changeListener.size(); i++) {
            changeListener.get(i).stateChanged(e);
        }
    }

    private void fireUpdateEvent(double dt) {
        UpdateEvent e = new UpdateEvent(this, dt);
        for (int i = 0; i < updateListener.size(); i++) {
            updateListener.get(i).update(e);
        }
    }
}
